
package com.bluelinelabs.conductor.conductor;

import ohos.aafwk.content.IntentParams;
import ohos.agp.components.Component;
import ohos.agp.components.ComponentContainer;
import ohos.agp.components.ComponentParent;

import com.bluelinelabs.conductor.conductor.changehandler.SimpleSwapChangeHandler;
import com.bluelinelabs.conductor.conductor.internal.ClassUtils;

import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * lipeiquan
 *
 * @since 2021-05-06
 */
public abstract class ControllerChangeHandler {

    static final Map<String, ChangeHandlerData> inProgressChangeHandlers = new HashMap<>();
    private static final String KEY_CLASS_NAME = "ControllerChangeHandler.className";
    private static final String KEY_SAVED_STATE = "ControllerChangeHandler.savedState";

    private boolean hasBeenUsed;

    boolean forceRemoveViewOnPush;

    public ControllerChangeHandler() {
        ensureDefaultConstructor();
    }


    public interface ControllerChangeListener {
        void onChangeStarted(Controller to, Controller from, boolean isPush, ComponentContainer container, ControllerChangeHandler handler);

        void onChangeCompleted(Controller to, Controller from, boolean isPush, ComponentContainer container, ControllerChangeHandler handler);
    }

    public void completeImmediately() {
    }

    public void restoreFromBundle(IntentParams bundle) {
    }

    public void setForceRemoveViewOnPush(boolean force) {
        forceRemoveViewOnPush = force;
    }


    private void ensureDefaultConstructor() {
        try {
            getClass().getConstructor();
        } catch (Exception e) {
            throw new RuntimeException(getClass() + " does not have a default constructor.");
        }
    }

    protected void onEnd() {
    }


    public ControllerChangeHandler copy() {
        return fromBundle(toBundle());
    }

    final IntentParams toBundle() {
        IntentParams bundle = new IntentParams();
        bundle.setParam(KEY_CLASS_NAME, getClass().getName());

        IntentParams savedState = new IntentParams();
        saveToBundle(savedState);
        bundle.setParam(KEY_SAVED_STATE, savedState);

        return bundle;
    }

    public boolean isReusable() {
        return false;
    }

    public void onAbortPush(ControllerChangeHandler newHandler, Controller newTop) {
    }

    public abstract void performChange(ComponentContainer container, Component from, Component to, boolean isPush, ControllerChangeCompletedListener changeListener);

    public void saveToBundle(IntentParams bundle) {
    }


    static void abortOrComplete(Controller toAbort, Controller newController, ControllerChangeHandler newChangeHandler) {
        ChangeHandlerData changeHandlerData = inProgressChangeHandlers.get(toAbort.getInstanceId());
        if (changeHandlerData != null) {
            if (changeHandlerData.isPush) {
                changeHandlerData.changeHandler.onAbortPush(newChangeHandler, newController);
            } else {
                changeHandlerData.changeHandler.completeImmediately();
            }

            inProgressChangeHandlers.remove(toAbort.getInstanceId());
        }
    }

    public static ControllerChangeHandler fromBundle(IntentParams bundle) {
        if (bundle != null) {
            String className = (String) bundle.getParam(KEY_CLASS_NAME);
            ControllerChangeHandler changeHandler = ClassUtils.newInstance(className);
            //noinspection ConstantConditions
            changeHandler.restoreFromBundle((IntentParams) bundle.getParam(KEY_SAVED_STATE));
            return changeHandler;
        } else {
            return null;
        }
    }

    static void executeChange(final ChangeTransaction transaction) {
        executeChange(transaction.to, transaction.from, transaction.isPush, transaction.container, transaction.changeHandler, transaction.listeners);
    }

    private static void executeChange(final Controller to, final Controller from, final boolean isPush, final ComponentContainer container, final ControllerChangeHandler inHandler, final List<ControllerChangeListener> listeners) {

        if (container != null) {
            final ControllerChangeHandler handler;
            if (inHandler == null) {
                handler = new SimpleSwapChangeHandler();
            } else if (inHandler.hasBeenUsed && !inHandler.isReusable()) {
                handler = inHandler.copy();
            } else {
                handler = inHandler;
            }
            handler.hasBeenUsed = true;

            if (from != null) {
                if (isPush) {
                    completeHandlerImmediately(from.getInstanceId());
                } else {
                    abortOrComplete(from, to, handler);
                }
            }

            if (to != null) {
                inProgressChangeHandlers.put(to.getInstanceId(), new ChangeHandlerData(handler, isPush));
            }

            for (ControllerChangeListener listener : listeners) {
                listener.onChangeStarted(to, from, isPush, container, handler);
            }

            final ControllerChangeType toChangeType = isPush ? ControllerChangeType.PUSH_ENTER : ControllerChangeType.POP_ENTER;
            final ControllerChangeType fromChangeType = isPush ? ControllerChangeType.PUSH_EXIT : ControllerChangeType.POP_EXIT;

            final Component toView;
            if (to != null) {
                toView = to.inflate(container);
                to.changeStarted(handler, toChangeType);
            } else {
                toView = null;
            }

            final Component fromView;
            if (from != null) {
                fromView = from.getView();
                from.changeStarted(handler, fromChangeType);
            } else {
                fromView = null;
            }
            handler.performChange(container, fromView, toView, isPush, new ControllerChangeCompletedListener() {
                @Override
                public void onChangeCompleted() {
                    if (from != null) {
                        from.changeEnded(handler, fromChangeType);
                    }

                    if (to != null) {
                        inProgressChangeHandlers.remove(to.getInstanceId());
                        to.changeEnded(handler, toChangeType);
                    }

                    for (ControllerChangeListener listener : listeners) {
                        listener.onChangeCompleted(to, from, isPush, container, handler);
                    }

                    if (handler.forceRemoveViewOnPush && fromView != null) {
                        ComponentParent fromParent = fromView.getComponentParent();
                        if (fromParent != null && fromParent instanceof ComponentContainer) {
                            ((ComponentContainer) fromParent).removeComponent(fromView);
                        }
                    }

                    if (handler.removesFromViewOnPush() && from != null) {
                        from.setNeedsAttach(false);
                    }
                }
            });
        }
    }

    static class ChangeTransaction {
        final Controller to;
        final Controller from;
        final boolean isPush;
        final ComponentContainer container;
        final ControllerChangeHandler changeHandler;
        final List<ControllerChangeListener> listeners;

        public ChangeTransaction(Controller to, Controller from, boolean isPush, ComponentContainer container, ControllerChangeHandler changeHandler, List<ControllerChangeListener> listeners) {
            this.to = to;
            this.from = from;
            this.isPush = isPush;
            this.container = container;
            this.changeHandler = changeHandler;
            this.listeners = listeners;
        }
    }

    public boolean removesFromViewOnPush() {
        return true;
    }

    static boolean completeHandlerImmediately(String controllerInstanceId) {
        ChangeHandlerData changeHandlerData = inProgressChangeHandlers.get(controllerInstanceId);
        if (changeHandlerData != null) {
            changeHandlerData.changeHandler.completeImmediately();
            inProgressChangeHandlers.remove(controllerInstanceId);
            return true;
        }
        return false;
    }

    /**
     * A simplified listener for being notified when the change is complete. This MUST be called by any custom
     * ControllerChangeHandlers in order to ensure that {@link Controller}s will be notified of this change.
     */
    public interface ControllerChangeCompletedListener {
        /**
         * Called when the change is complete.
         */
        void onChangeCompleted();
    }

    private static class ChangeHandlerData {
        public final ControllerChangeHandler changeHandler;
        public final boolean isPush;

        public ChangeHandlerData(ControllerChangeHandler changeHandler, boolean isPush) {
            this.changeHandler = changeHandler;
            this.isPush = isPush;
        }
    }


}
